﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace SolutionSorter.Core
{
    public class ContentSorter : IContentSorter
    {
        IConfiguration _config;

        public ContentSorter(IConfiguration config)
        {
            _config = config;
        }

        public string SortContent(string content)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(ExtractPreProjectContent(content));
            builder.Append(SortProjectContent(ExtractNestedProjectsContent(content), ParseProjectContent(ExtractProjectContent(content))));
            builder.Append(ExtractPostProjectContent(content));
            return builder.ToString();
        }

        public string ExtractPreProjectContent(string content)
        {
            var idx = content.IndexOf(_config.PreProjectSectionToken);
            return content.Substring(0, idx + _config.PreProjectSectionToken.Length);
        }

        public string ExtractProjectContent(string content)
        {
            var startIdx = content.IndexOf(_config.PreProjectSectionToken) + _config.PreProjectSectionToken.Length;
            var endIdx = content.IndexOf(_config.PostProjectSectionToken) - startIdx;
            return content.Substring(startIdx, endIdx);
        }

        public string ExtractPostProjectContent(string content)
        {
            var idx = content.IndexOf(_config.PostProjectSectionToken);
            return content.Substring(idx);
        }

        public string ExtractNestedProjectsContent(string content)
        {
            var startIdx = content.IndexOf(_config.NestedProjectsStartToken);
            var endIdx = content.IndexOf(_config.NestedProjectsEndToken, startIdx) - startIdx + _config.NestedProjectsEndToken.Length;
            return content.Substring(startIdx, endIdx);
        }

        public Dictionary<Guid, SolutionItem> ParseProjectContent(string content)
        {
            var solutionItems = new Dictionary<Guid, SolutionItem>();

            bool done = false;
            var lastStartIdx = 0;
            var contentLength = content.Length;
            do
            {
                var startIdx = content.IndexOf(_config.ProjectStartToken, lastStartIdx);

                if (startIdx == -1)
                {
                    done = true;
                    continue;
                }

                var endIdx = content.IndexOf(_config.ProjectEndToken, startIdx) + _config.ProjectEndToken.Length;
                var projectItem = content.Substring(startIdx, endIdx - startIdx);

                var solutionItem = ParseSolutionItem(projectItem);
                if (solutionItem != null)
                {
                    solutionItems.Add(solutionItem.Id, solutionItem);
                }

                lastStartIdx = startIdx + 1;

            } while (!done);

            return solutionItems;
        }

        public string SortProjectContent(string content, Dictionary<Guid, SolutionItem> solutionItems)
        {
            var startIdx = 0;
            do
            {
                var endIdx = content.IndexOf("\r\n", startIdx) + 2;

                var projectItem = content.Substring(startIdx, endIdx - startIdx);
                ParseNestedProject(projectItem, solutionItems);

                startIdx = endIdx;

            } while (startIdx < content.Length);

            // Convert solution items into a tree.
            var allChildren = new List<SolutionItem>();

            // Add children to their parents.
            foreach (var pair in solutionItems)
            {
                SolutionItem solutionItem = pair.Value;
                if (solutionItems.ContainsKey(solutionItem.ParentId))
                {
                    SolutionItem parentSolutionItem = solutionItems[solutionItem.ParentId];
                    parentSolutionItem.AddChild(solutionItem);
                    allChildren.Add(solutionItem);
                }
            }

            // Remove from the top level of the tree all children who have been added to a parent 
            // within the tree.
            foreach (var item in allChildren)
            {
                if (solutionItems.ContainsKey(item.Id))
                {
                    solutionItems.Remove(item.Id);
                }
            }

            // Add top level tree items to the root list.
            var sortedSolutionItems = new SortedList<string, SolutionItem>(solutionItems.Count);
            foreach (var item in solutionItems)
            {
                sortedSolutionItems.Add(item.Value.Name, item.Value);
            }

            StringBuilder projectContentBuilder = new StringBuilder();
            FormatProjectContent(sortedSolutionItems, projectContentBuilder);
            return projectContentBuilder.ToString();
        }

        private void FormatProjectContent(SortedList<string, SolutionItem> solutionItems, StringBuilder content)
        {
            foreach (var item in solutionItems)
            {
                content.Append(item.Value.Text);
                FormatProjectContent(item.Value.Children, content);
            }
        }

        private SolutionItem ParseSolutionItem(string text)
        {
            SolutionItem solutionItem = null;

            // "Project(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"MyProject\", \"MyProject\", \"{FC26EBA3-5062-4B2C-AF92-3A113A2DEDBF}\"\r\nEndProject\r\n"
            Match match = Regex.Match(text, "^.*\\s*=\\s*\"([^\"]+)\"\\s*,\\s*\"([^\"]*)\"\\s*,\\s*\"(\\{.+\\})\"");
            if (match.Success &&
                match.Groups.Count > 3 &&
                match.Groups[1].Captures.Count > 0 &&
                match.Groups[2].Captures.Count > 0 &&
                match.Groups[3].Captures.Count > 0)
            {
                string item1 = match.Groups[1].Captures[0].Value;
                string item2 = match.Groups[2].Captures[0].Value;
                string item3 = match.Groups[3].Captures[0].Value;
                solutionItem = new SolutionItem(new Guid(item3), item1, item2, text);
            }

            return solutionItem;
        }

        private void ParseNestedProject(string text, Dictionary<Guid, SolutionItem> solutionItems)
        {
            // "Project(\"{BEFFF20B-BD7E-49C7-8373-2867CF1C6D62} = {F5134A4C-8E9F-4E5A-A389-237FD9604121}"
            Match match = Regex.Match(text, "^\\s*(\\{.+\\})\\s*=\\s*(\\{.+\\})\\s*$");
            if (match.Success &&
                match.Groups.Count > 2 &&
                match.Groups[1].Captures.Count > 0 &&
                match.Groups[2].Captures.Count > 0)
            {
                string item1 = match.Groups[1].Captures[0].Value;
                string item2 = match.Groups[2].Captures[0].Value;
                Guid projectId = new Guid(item1);
                Guid parentId = new Guid(item2);
                if (solutionItems.ContainsKey(projectId))
                {
                    var solutionItem = solutionItems[projectId];
                    if (solutionItem != null)
                    {
                        solutionItem.ParentId = parentId;
                        solutionItem.NestedProjectText = text;
                    }
                }
            }
        }
    }
}
